// 版权归Go作者所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// Goroutine抢占
// 
// Goroutine可以在任何安全点被抢占。目前，有几类安全点：
// 
// 1。阻塞的安全点出现在goroutine被
// 取消调度、同步阻塞或系统调用期间。
// 
// 2。当运行的goroutine检查
// 是否有抢占请求时，就会出现同步安全点。
// 
// 3。异步安全点出现在用户代码
// 中的任何指令上，其中goroutine可以安全暂停，而保守的
// 堆栈和寄存器扫描可以找到堆栈根。运行时可以使用信号在异步安全点停止goroutine。
// 
// 在阻塞和同步安全点，goroutine的CPU 
// 状态是最小的，垃圾收集器有关于其整个堆栈的完整信息
// 。这样就可以用最小的空间重新安排一个
// goroutine，并精确地扫描goroutine的
// 堆栈。
// 
// 通过重载堆栈来实现同步安全点
// 绑定签入函数序言。要在
// 下一个同步安全点抢占goroutine，运行时会将goroutine的
// 堆栈绑定到一个值，该值将导致下一个堆栈绑定检查
// 失败，并进入堆栈增长实现，该实现将
// 检测到它实际上是抢占并重定向到抢占
// 处理。
// 
// 异步安全点的抢占是通过使用操作系统机制（例如，信号）暂停
// 线程并检查其
// 状态来确定goroutine是否处于异步
// 安全点来实现的。由于线程挂起本身通常是
// 异步的，它还会检查正在运行的goroutine是否希望被
// 抢占，因为这可能已经改变了。如果所有条件都得到满足，它会调整信号上下文，使其看起来像刚刚被称为asyncPreempt的有信号的线程，并恢复线程。
// asyncPreempt溢出所有寄存器并进入调度程序。
// 
// （另一种选择是在信号处理程序本身中进行抢占。这将允许操作系统保存和恢复寄存器状态，而运行时只需要知道如何从信号上下文中提取可能包含寄存器的指针。然而，对于每个抢占的寄存器，这个
// 将消耗一个M，调度程序本身也会消耗一个M。）lf 
// 不是设计用来从信号处理程序运行的，因为它倾向于在抢占路径中分配内存和启动线程。）

package runtime

import (
	"internal/abi"
	"internal/goarch"
	"runtime/internal/atomic"
)

type suspendGState struct {
	g *g

	// dead表示goroutine没有暂停，因为它
	// dead。在观察到死掉的
	// 状态后，可以重新使用此goroutine，因此调用方不能认为它
	// 仍然死掉。
	dead bool

	// stopped表示此暂停通过G.preemptStop将G转换为
	// _Gwaiting，因此负责
	// /完成后的准备工作。
	stopped bool
}

// suspendG在安全点暂停goroutine gp，并返回已暂停goroutine的
// 状态。调用方获得对
// goroutine的读取权限，直到调用resumeG。
// 
// 多个呼叫者尝试同时挂起同一个
// goroutine是安全的。goroutine可以在
// 后续成功的挂起操作之间执行。当前的
// 实现授予对goroutine的独占访问权，因此
// 多个调用方将序列化。然而，其目的是授予
// 共享读访问权限，因此请不要依赖独占访问。
// 
// 这必须从系统堆栈中调用，并且
// 上的用户goroutine当前M（如果有）必须处于可抢占状态。此
// 可防止两个Goroutine试图挂起另一个
// 并且都处于不可抢占状态时出现死锁。
// 还有其他方法可以解决这个僵局，但这似乎很简单。
// 
// TODO（austin）：如果我们要求从
// 用户goroutine调用这个呢？然后我们可以在
// 等待时取消goroutine的调度，而不是阻塞线程。如果两个goroutines试图中止对方，其中一个会赢，另一个不会中止，直到恢复。我们必须小心，他们不能真正排队互相暂停，然后两人都被暂停。这也避免了在同步情况下需要内核上下文切换，因为我们可以直接调度服务程序。在
// 信号情况下，上下文切换是不可避免的。
// 
// go:systemstack 
func suspendG(gp *g) suspendGState {
	if mp := getg().m; mp.curg != nil && readgstatus(mp.curg) == _Grunning {
		// 由于我们在这个M的系统堆栈上，用户
		// G被卡在了一个不安全的点上。如果另一个goroutine 
		// 试图抢占m.curg，它可能会死锁。
		throw("suspendG from non-preemptible goroutine")
	}

	// 请参阅https:
	const yieldDelay = 10 * 1000
	var nextYield int64

	// 将goroutine驱动到抢占点。
	stopped := false
	var asyncM *m
	var asyncGen uint32
	var nextPreemptM int64
	for i := 0; ; i++ {
		switch s := readgstatus(gp); s {
		default:
			if s&_Gscan != 0 {
				// 其他人正在暂停它。等他们说完。
				// 
				// TODO:如果我们可以合并暂停，那就更好了。
				break
			}

			dumpgstatus(gp)
			throw("invalid g status")

		case _Gdead:
			// 没有要暂停的内容。
			// 
			// 抢占停止可能需要清除，但
			// 在这里这样做可能会与goroutine 
			// 重复使用竞争。相反，goexit0将其清除。
			return suspendGState{dead: true}

		case _Gcopystack:
			// 正在复制堆栈。我们需要等待
			// 直到完成。

		case _Gpreempted:
			// 我们（或其他人）暂停了G。通过将其转换为
			// 对其拥有所有权。
			// /\u Gwaiting，要求
			if !casGFromPreempted(gp, _Gpreempted, _Gwaiting) {
				break
			}

			// 我们停止了G，所以我们必须稍后准备。
			stopped = true

			s = _Gwaiting
			fallthrough

		case _Grunnable, _Gsyscall, _Gwaiting:
			// 通过设置扫描位来声明goroutine。
			// 这可能与gp的执行或准备工作竞争。
			// 扫描位使其不处于转换状态。
			if !castogscanstatus(gp, s, s|_Gscan) {
				break
			}

			// 清除抢占请求。重置堆栈保护是安全的，因为我们持有
			// /_Gscan位，因此拥有堆栈。
			gp.preemptStop = false
			gp.preempt = false
			gp.stackguard0 = gp.stack.lo + _StackGuard

			// goroutine已经处于安全点
			// 我们现在已经将其锁定。
			// 
			// TODO:如果我们不
			// 将其留在_Gscan中，那会好得多，但相反，
			// 会在恢复之前轻轻阻止它的日程安排。
			// 也许我们只使用它来跳一个暂停的
			// 计数，而调度程序跳过暂停的
			// goroutines？这对
			// /{u Gsyscall，{u Gwaiting}->u Grunning来说还不够。也许
			// 对于所有这些转换，我们需要检查
			// 暂停和取消计划？
			return suspendGState{g: gp, stopped: stopped}

		case _Grunning:
			// 优化：如果已经有一个挂起的抢占请求
			// （来自上一个循环迭代），那么就不用再费心原子了。
			if gp.preemptStop && gp.preempt && gp.stackguard0 == stackPreempt && asyncM == gp.m && atomic.Load(&asyncM.preemptGen) == asyncGen {
				break
			}

			// 暂时阻止状态转换。
			if !castogscanstatus(gp, _Grunning, _Gscanrunning) {
				break
			}

			// 请求同步抢占。
			gp.preemptStop = true
			gp.preempt = true
			gp.stackguard0 = stackPreempt

			// 准备异步抢占。
			asyncM2 := gp.m
			asyncGen2 := atomic.Load(&asyncM2.preemptGen)
			needAsync := asyncM != asyncM2 || asyncGen != asyncGen2
			asyncM = asyncM2
			asyncGen = asyncGen2

			casfrom_Gscanstatus(gp, _Gscanrunning, _Grunning)

			// 发送异步抢占。我们这样做是因为抢占可能是同步的，我们不想看到G只是在它的状态上旋转。
			if preemptMSupported && debug.asyncpreemptoff == 0 && needAsync {
				// 速率限制抢占呼叫。这是
				// 在Windows上尤其重要
				// 其中preemptM实际上是
				// 同步的，这里的自旋循环
				// 可以导致活锁。
				now := nanotime()
				if now >= nextPreemptM {
					nextPreemptM = now + yieldDelay/2
					preemptM(asyncM)
				}
			}
		}

		// 待办事项：不要忙着等待。这个循环实际上应该只有
		// 是一个简单的读/决定/CAS循环，只有在
		// 有一个活跃的竞争时才会失败。一旦CAS成功，我们
		// 应该排队抢占（这将要求
		// 它在_Grunning案例中是可靠的，而不是
		// 尽最大努力），然后睡觉，直到我们接到通知
		// goroutine被暂停。
		if i == 0 {
			nextYield = nanotime() + yieldDelay
		}
		if nanotime() < nextYield {
			procyield(10)
		} else {
			osyield()
			nextYield = nanotime() + yieldDelay/2
		}
	}
}

// resumeG取消暂停的效果，允许暂停的
// goroutine从当前安全点继续运行。
func resumeG(state suspendGState) {
	if state.dead {
		// 我们实际上没有阻止任何事情。
		return
	}

	gp := state.g
	switch s := readgstatus(gp); s {
	default:
		dumpgstatus(gp)
		throw("unexpected g status")

	case _Grunnable | _Gscan,
		_Gwaiting | _Gscan,
		_Gsyscall | _Gscan:
		casfrom_Gscanstatus(gp, s, s&^_Gscan)
	}

	if state.stopped {
		// 我们停止了，所以我们需要重新安排。
		ready(gp, 0, true)
	}
}

// canPreemptM报告mp是否处于可安全抢占的状态。
// 
// 它是nosplit，因为它有nosplit调用者。
// 
// go:nosplit 
func canPreemptM(mp *m) bool {
	return mp.locks == 0 && mp.mallocing == 0 && mp.preemptoff == "" && mp.p.ptr().status == _Prunning
}

// go:generate go run mkpreempt。加油

// asyncPreempt保存所有用户寄存器和调用asyncPreempt2。
// 
// 当堆栈扫描遇到异步抢占帧时，它会保守地扫描该
// 帧及其父帧。
// 
// asyncPreempt在汇编中实现。
func asyncPreempt()

// go:nosplit 
func asyncPreempt2() {
	gp := getg()
	gp.asyncSafePoint = true
	if gp.preemptStop {
		mcall(preemptPark)
	} else {
		mcall(gopreempt_m)
	}
	gp.asyncSafePoint = false
}

// asyncPreemptStack是注入
// asyncPreempt调用所需的堆栈空间字节。
var asyncPreemptStack = ^uintptr(0)

func init() {
	f := findfunc(abi.FuncPCABI0(asyncPreempt))
	total := funcMaxSPDelta(f)
	f = findfunc(abi.FuncPCABIInternal(asyncPreempt2))
	total += funcMaxSPDelta(f)
	// 为退货电脑等增加一些开销。
	asyncPreemptStack = uintptr(total) + 8*goarch.PtrSize
	if asyncPreemptStack > _StackLimit {
		// 我们需要的不仅仅是nosplit限制。这不是
		// 不安全，但它可能会限制异步抢占。
		// 
		// 如果我们开始使用更多
		// 寄存器，这可能会是一个问题。在这种情况下，我们应该将寄存器
		// 存储在上下文对象中。如果我们为每个P预先分配一个，
		// asyncPreempt只会将几个寄存器溢出到
		// 堆栈中，然后抓取其上下文对象并溢出到
		// 堆栈中。当它进入运行时，它将为P.
		print("runtime: asyncPreemptStack=", asyncPreemptStack, "\n")
		throw("async stack too large")
	}
}

// wantAsyncPreempt返回异步抢占是否为排队等待gp的
// 分配一个新上下文。
func wantAsyncPreempt(gp *g) bool {
	// 检查G和P。
	return (gp.preempt || gp.m.p != 0 && gp.m.p.ptr().preempt) && readgstatus(gp)&^_Gscan == _Grunning
}

// isAsyncSafePoint报告指令PC上的gp是否为
// 异步安全点。这表示：
// 
// 1。挂起gp并保守地扫描其堆栈和
// 寄存器是安全的。没有潜在隐藏的指针值，它的abcDef
// 
// 2。gp有足够的堆栈空间来注入asyncPreempt调用。
// 
// 3。与运行时交互通常是安全的，即使我们是
// 在这里停止的信号处理程序中。例如，没有运行时
// 锁，因此获取运行时锁不会自动死锁。
// 
// 在某些情况下，PC可以安全地进行异步抢占，但它
// 还需要调整恢复PC。新PC将以
// 返回第二个结果。
func isAsyncSafePoint(gp *g, pc, sp, lr uintptr) (bool, uintptr) {
	mp := gp.m

	// 只有用户Gs才能拥有安全点数。我们检查第一个
	// 因为在处理这个G抢占的
	// 调度程序中捕获mp是非常常见的。
	if mp.curg != gp {
		return false, 0
	}

	// 检查M状态。
	if mp.p == 0 || !canPreemptM(mp) {
		return false, 0
	}

	// 检查堆栈空间。
	if sp < gp.stack.lo || sp-gp.stack.lo < asyncPreemptStack {
		return false, 0
	}

	// 检查PC是否为不安全点。
	f := findfunc(pc)
	if !f.valid() {
		// 不走代码。
		return false, 0
	}
	if (GOARCH == "mips" || GOARCH == "mipsle" || GOARCH == "mips64" || GOARCH == "mips64le") && lr == pc+8 && funcspdelta(f, pc, nil) == 0 {
		// 我们可能在执行了一半的CALL指令时停止，
		// LR已更新，但PC尚未更新。如果我们抢占
		// 这里我们会看到一个看似自递归的调用，它在
		// 中，实际上不是。
		// 这通常是可以的，因为我们使用保存在
		// 堆栈上的返回地址来展开，而不是LR值。但是如果这是对morestack的调用，我们还没有创建帧，我们将使用LR进行解卷，这将是不好的。
		return false, 0
	}
	up, startpc := pcdatavalue2(f, _PCDATA_UnsafePoint, pc)
	if up == _PCDATA_UnsafePointUnsafe {
		// 编译器标记的不安全点。这包括
		// 原子序列（例如写屏障）和nosplit 
		// 函数（调用时除外）。这是汇编代码。不要以为这是好的。
		return false, 0
	}
	if fd := funcdata(f, _FUNCDATA_LocalsPointerMaps); fd == nil || f.flag&funcFlag_ASM != 0 {
		// TODO:根据经验，我们仍然需要fd==nil检查。为什么？
		// 
		// TODO:是否存在安全但没有
		// 局部指针映射的情况，如空帧函数？
		// 可能会抢占任何汇编函数
		// 除了在f.flag中设置了funcFlag_SPWRITE的函数。
		return false, 0
	}
	name := funcname(f)
	if inldata := funcdata(f, _FUNCDATA_InlTree); inldata != nil {
		inltree := (*[1 << 20]inlinedCall)(inldata)
		ix := pcdatavalue(f, _PCDATA_InlTreeIndex, pc, nil)
		if ix >= 0 {
			name = funcnameFromNameoff(f, inltree[ix].func_)
		}
	}
	if hasPrefix(name, "runtime.") ||
		hasPrefix(name, "runtime/internal/") ||
		hasPrefix(name, "reflect.") {
		// 目前，我们从未异步抢占运行时或
		// 任何与运行时密切相关的内容。已知问题
		// 包括：调度程序中的各个点（“不要
		// 在此处和此处之间抢占”）、大部分延迟
		// 实现（堆栈上的非类型化信息）、大容量写入
		// 屏障（写入屏障检查）、
		// 反射。{makeFuncStub，methodValueCall}。
		// 
		// TODO（奥斯汀）：我们应该改进这一点，或者逐步选择一些东西。
		return false, 0
	}
	switch up {
	case _PCDATA_Restart1, _PCDATA_Restart2:
		// 可重启指令序列。退出PC到
		// 启动PC。
		if startpc == 0 || startpc > pc || pc-startpc > 20 {
			throw("bad restart PC")
		}
		return true, startpc
	case _PCDATA_RestartAtEntry:
		// 在恢复时从函数项重新启动。
		return true, f.entry()
	}
	return true, pc
}
